// route.tsx
import React, {
  type JSX,
  useMemo,
  useState,
  useContext,
  useCallback,
  createContext,
  Fragment
} from 'react'

/**
 * 路由配置项
 */
export type RouteObject = {
  /**
   * 路由路径
   */
  path: string
  /**
   * 组件（支持懒加载）
   */
  element?: React.ReactNode | JSX.Element | null
  /**
   * 子路由
   */
  children?: RouteObject[]
  /**
   * 父路由的名字
   */
  parentName?: string
}

interface ILink {
  to: string
  query?: Record<string, any>
  params?: Record<string, any>
  children: React.ReactNode
  className?: string
  activeClassName?: string
  replace?: boolean
}

export interface RouterContextType {
  currentRoute: HistoryItem
  push: (path: string | HistoryItem) => void
  replace: (path: string | HistoryItem) => void
  go: (delta: number) => void
  back: () => void
  forward: () => void
  replaceHistory: (path: string | HistoryItem, stackIndex: number) => void
}

export interface OutletContextType {
  outletStack: RouteObject[]
}

// 历史记录项
export type HistoryItem<T extends Record<string, any> = Record<string, any>> = {
  events?: Map<string, (...args: any[]) => void> | null
  path: string
  params?: T
  query?: T
}

const RouterContext = createContext<RouterContextType | null>(null)
const OutletContext = createContext<OutletContextType | null>(null)
let globalIndex = 0

interface RouterProps {
  children?: React.ReactNode
  routes?: RouteObject[]
  query?: Record<string, any>
  params?: Record<string, any>
  initialPath?: string
  mode?: Mode
}

type Mode = 'sessionStorage' | 'localStorage'

export function cacheStore({
  prefix = '',
  mode = 'sessionStorage'
}: {
  mode: Mode
  prefix?: string
}) {
  let storage: Storage = window[mode]

  return {
    setMode(m: Mode) {
      storage = window[m]
    },
    get<T>(key: string): T | null {
      try {
        const cache = storage.getItem(prefix + key)
        return cache ? JSON.parse(cache) : null
      } catch (e) {
        console.error(`Error parsing cache for key: ${key}`, e)
        return null
      }
    },
    set<T>(key: string, newValue: T) {
      try {
        storage.setItem(prefix + key, JSON.stringify(newValue))
      } catch (e) {
        console.error(`Error setting cache for key: ${key}`, e)
      }
    },
    remove(key: string) {
      storage.removeItem(prefix + key)
    }
  }
}

const GLOBAL_ROUTE_KEY = 'GLOBAL_VIRTUAL_ROUTE_KEY_fdgfdnlejroijyhn_'
const cache = cacheStore({ prefix: GLOBAL_ROUTE_KEY, mode: 'sessionStorage' })

const setSubRoutes = (
  currentRoute: RouteObject,
  currentPath: string
): RouteObject[] => {
  const newOutletStack: RouteObject[] = []
  const segments = currentPath.split('/').filter(Boolean)

  segments.forEach((path) => {
    const curRoute =
      newOutletStack.length === 0
        ? currentRoute
        : newOutletStack[newOutletStack.length - 1]

    if (!curRoute.children) return

    const route = curRoute.children.find(
      (item) => item.path.replace(/^\//, '') === path
    )

    if (route) {
      route.parentName = route.parentName || curRoute.path
      newOutletStack.push(route)
    }
  })

  return newOutletStack
}

const matchRoute = (currentRoutePath: string, route: RouteObject): boolean => {
  return (
    currentRoutePath === route.path ||
    (route.path !== '/' && currentRoutePath.startsWith(route.path + '/'))
  )
}

const renderRoutes = (
  currentRoutePath: string,
  routeConfig: RouteObject[]
): {
  RouteElement: React.ReactNode | null
  OutletStack: RouteObject[]
} => {
  for (const route of routeConfig) {
    if (!matchRoute(currentRoutePath, route)) continue

    const { element: RouteElement, children } = route

    if (RouteElement) {
      if (children && children.length > 0) {
        globalIndex = 0
        const OutletStack = setSubRoutes(route, currentRoutePath)
        return { RouteElement, OutletStack }
      }
      return { RouteElement, OutletStack: [] }
    }
  }

  return { RouteElement: null, OutletStack: [] }
}

export function Router({
  children = null,
  routes = [],
  initialPath = '/',
  mode = 'sessionStorage',
  query = {},
  params = {}
}: RouterProps) {
  cache.setMode(mode)

  const [currentIndex, setCurrentIndex] = useState<number>(
    () => cache.get('currentIndex') ?? 0
  )

  const [historyStack, setHistoryStack] = useState<HistoryItem[]>(
    () =>
      cache.get<HistoryItem[]>('historyStack') || [
        { path: initialPath, query, params }
      ]
  )
  const cacheHistoryList = useCallback((stack: HistoryItem[]) => {
    cache.set(
      'historyStack',
      stack.map((e) => ({
        path: e.path,
        query: e.query
      }))
    )
  }, [])

  const push = useCallback(
    (to: string | HistoryItem) => {
      const nextPathname = typeof to === 'string' ? to : to.path
      const nextQuery = typeof to === 'string' ? {} : to.query || {}
      const nextParams = typeof to === 'string' ? {} : to.params || {}

      setHistoryStack((prev) => {
        const newStack = [
          ...prev.slice(0, currentIndex + 1),
          { path: nextPathname, query: nextQuery, params: nextParams }
        ]
        // 更新缓存
        cacheHistoryList(newStack)
        return newStack
      })

      setCurrentIndex((prev) => {
        const newIndex = prev + 1
        // 同步更新索引缓存
        cache.set('currentIndex', newIndex)
        return newIndex
      })
    },
    [currentIndex]
  )

  const replace = useCallback(
    (to: string | HistoryItem) => {
      const nextPathname = typeof to === 'string' ? to : to.path
      const nextQuery = typeof to === 'string' ? {} : to.query || {}
      const nextParams = typeof to === 'string' ? {} : to.params || {}

      setHistoryStack((prev) => {
        const newHistoryStack = [...prev]
        newHistoryStack[currentIndex] = {
          path: nextPathname,
          query: nextQuery,
          params: nextParams
        }
        // 同步更新缓存
        cacheHistoryList(newHistoryStack)
        return newHistoryStack
      })
    },
    [currentIndex]
  )

  const replaceHistory = useCallback(
    (to: string | HistoryItem, stackIndex: number) => {
      const nextPathname = typeof to === 'string' ? to : to.path
      const nextQuery = typeof to === 'string' ? {} : to.query || {}
      const nextParams = typeof to === 'string' ? {} : to.params || {}
      const newHistoryStack = [...historyStack]
      newHistoryStack[
        typeof stackIndex === 'number' ? stackIndex : currentIndex
      ] = {
        path: nextPathname,
        query: nextQuery,
        params: nextParams
      }
      // 同步更新缓存
      cacheHistoryList(newHistoryStack)
    },
    [currentIndex, historyStack]
  )

  const go = useCallback(
    (delta: number) => {
      setCurrentIndex((prev) => {
        const newIndex = Math.max(
          0,
          Math.min(prev + delta, historyStack.length - 1)
        )
        // 直接同步更新索引缓存
        cache.set('currentIndex', newIndex)
        return newIndex
      })
    },
    [historyStack.length]
  )

  const back = useCallback(() => {
    go(-1)
  }, [go])

  const forward = useCallback(() => {
    go(1)
  }, [go])

  const currentRoute = useMemo(() => {
    return (
      historyStack[currentIndex] || {
        path: '/',
        query: {},
        params: {}
      }
    )
  }, [historyStack, currentIndex])

  const { RouteElement, OutletStack } = useMemo(() => {
    currentRoute.events = null
    return renderRoutes(currentRoute.path, routes)
  }, [currentRoute.path, routes])

  const contextValue = useMemo<RouterContextType>(
    () => ({
      go,
      push,
      back,
      forward,
      replace,
      currentRoute,
      replaceHistory
    }),
    [currentRoute, push, replace, go, back, forward, replaceHistory]
  )

  const outContextValue = useMemo<OutletContextType>(
    () => ({
      outletStack: OutletStack
    }),
    [OutletStack]
  )

  return (
    <RouterContext.Provider value={contextValue}>
      <OutletContext.Provider value={outContextValue}>
        {routes.length > 0 ? RouteElement : children}
      </OutletContext.Provider>
    </RouterContext.Provider>
  )
}

/**
 * 导航钩子
 * @returns {object} 导航对象
 * @example
 * const { push, query, params, replace, go, back, forward } = useNavigate()
 */
export function useNavigate<
  Q = Record<string, any>,
  P = Record<string, any>
>() {
  const context = useContext(RouterContext)

  if (!context) {
    throw new Error('useNavigate must be used within a Router')
  }

  const {
    push,
    replace,
    replaceHistory,
    currentRoute: { path, query = {}, params = {} }
  } = context
  const on = useCallback(
    (eventName: string, callback: (...args: any[]) => void) => {
      if (!context.currentRoute.events) {
        context.currentRoute.events = new Map<
          string,
          (...args: any[]) => void
        >()
      }
      context.currentRoute.events.set(eventName, callback)
    },
    [context]
  )

  const emit = useCallback(
    (eventName: string, ...args: any[]) => {
      if (!context.currentRoute.events) {
        return
      }

      const eventNames = eventName.split(',')
      for (const [name, callback] of context.currentRoute.events.entries()) {
        if (eventNames.includes(name) || eventName === '*') {
          callback(...args)
        }
      }
    },
    [context]
  )

  return {
    on,
    emit,
    push,
    path,
    query: query as Q,
    params: params as P,
    replace,
    go: context.go,
    replaceHistory,
    back: context.back,
    forward: context.forward
  }
}

/**
 * 路由链接组件
 * @param {string} to 路由地址
 * @param {object} query 查询参数
 * @param {object} params 路由参数
 * @param {boolean} replace 是否替换当前路由
 * @returns {React.ReactNode} 路由链接组件
 * @example
 * <Link to="/user/123" query={{ name: '张三' }} params={{ id: 123 }} replace={true}></Link>
 */
export function Link({
  to,
  query = {},
  params = {},
  children,
  className = 'link',
  activeClassName = 'active-link',
  replace: replaceProp = false // 默认应为false，即默认push而不是replace
}: ILink) {
  const context = useContext(RouterContext)

  if (!context) {
    throw new Error('Link must be used within a Router')
  }

  const { push, replace: replaceFn } = context

  const handleClick = (e: React.MouseEvent) => {
    e.preventDefault()
    if (replaceProp) {
      replaceFn({ path: to, query, params })
    } else {
      push({ path: to, query, params })
    }
  }

  const isActive = context.currentRoute.path === to

  return (
    <a
      href={to}
      onClick={handleClick}
      className={`${className} ${isActive ? activeClassName : ''}`}
    >
      {children}
    </a>
  )
}

/**
 * 子路由组件
 * @param {string} name 占位路由父级名称
 * @returns {React.ReactNode} 子路由组件
 */
export function Outlet({
  name,
  forceKey = ''
}: {
  name?: string
  forceKey?: string
}): JSX.Element | null {
  const context = useContext(OutletContext)

  if (!context) {
    return null
  }

  const { outletStack } = context
  if (!outletStack) {
    return null
  }

  const el = name
    ? outletStack.find(
        (e) => e?.parentName?.replace(/^\//, '') === name.replace(/^\//, '')
      )?.element
    : outletStack[globalIndex]?.element

  globalIndex++

  // 移除了对key属性的处理，因为key是React的保留属性，不应作为props传递
  if (React.isValidElement(el)) {
    return forceKey ? <Fragment key={forceKey}>{el}</Fragment> : el
  }

  return null
}

/**
 * 创建路由
 * @param {RouteObject[]} routes 路由配置
 * @returns {RouteObject[]} 路由配置
 */
export function createRoutes(routes: RouteObject[]): RouteObject[] {
  return routes
}

/**
 * 路由params参数获取
 * @returns {Record<string,any>} params参数
 */
export function useParams<
  T extends Record<string, any> = Record<string, any>
>(): T {
  const context = useNavigate()
  return (context?.params || {}) as T
}

/**
 * 路由query参数获取
 * @returns {Record<string,any>} query参数
 */
export function useQuery<
  T extends Record<string, any> = Record<string, any>
>(): T {
  const context = useNavigate()
  return (context?.query || {}) as T
}
